
local addonName, addonTable = ...
local zc = addonTable.zc
local zz = zc.md
local _

KM_NULL_STATE	= 0;
KM_PREQUERY		= 1;
KM_INQUERY		= 2;
KM_POSTQUERY	= 3;
KM_ANALYZING	= 4;
KM_SETTINGSORT	= 5;

local AUCTION_CLASS_WEAPON		 = 1;
local AUCTION_CLASS_ARMOR 		 = 2;
local AUCTION_CLASS_BATTLEPET	 = 11;

local gAllScans = {};

local BATTLE_PET_ITEMID = 82800

local BIGNUM = 999999999999;

local ATR_SORTBY_NAME_ASC = 0;
local ATR_SORTBY_NAME_DES = 1;
local ATR_SORTBY_PRICE_ASC = 2;
local ATR_SORTBY_PRICE_DES = 3;

gScanHistDayZero = time({year=2010, month=11, day=15, hour=0});		-- never ever change

local gNumNilItemLinks

-----------------------------------------

AtrScan = {};
AtrScan.__index = AtrScan;

-----------------------------------------

AtrSearch = {};
AtrSearch.__index = AtrSearch;

-----------------------------------------

local function GetExactMatchText (searchText)

	local emtext = nil;
	
	if (zc.StringStartsWith (searchText, "\"") and zc.StringEndsWith (searchText, "\"")) then
		emtext = string.sub (searchText, 2, searchText:len()-1);
	end

	return emtext;
end

-----------------------------------------

function Atr_NewSearch (itemName, IDstring, itemLink, rescanThreshold)

	local srch = {};
	setmetatable (srch, AtrSearch);
	srch:Init (itemName, IDstring, itemLink, rescanThreshold);

	return srch;
end

-----------------------------------------

function AtrSearch:Init (searchText, IDstring, itemLink, rescanThreshold)

	if (searchText == nil) then
		searchText = ""
	end

	self.origSearchText = searchText
	
	self.exactMatchText	= nil;
	self.searchText		= searchText
	
	if (IDstring == nil) then
		self.exactMatchText = GetExactMatchText(searchText)
		if (self.exactMatchText) then
			self.searchText = self.exactMatchText
		end
	end		

	self.IDstring			= IDstring
	self.processing_state	= KM_NULL_STATE
	self.current_page		= -1
	self.items				= {}
	self.query				= Atr_NewQuery()
	self.sortedScans		= nil
	self.sortHow			= ATR_SORTBY_PRICE_ASC
	self.shopListIndex		= 1
	self.shplist			= Atr_GetShoppingListFromSearchText (self.searchText)

	if (Atr_IsCompoundSearch(self.searchText)) then
		_, _, _, _, _, self.minItemLevel, self.maxItemLevel = Atr_ParseCompoundSearch (self.searchText);
	end
	
	if (IDstring) then	

		if (rescanThreshold and rescanThreshold > 0) then
			local scan = Atr_FindScan (IDstring, searchText);
			if (scan and (time() - scan.whenScanned) <= rescanThreshold) then
				self.items[IDstring] = scan;
				self.items[IDstring]:UpdateItemLink (itemLink);
			end
		end
		
		if (not self.items[IDstring]) then		
			self.items[IDstring] = Atr_FindScanAndInit (IDstring, searchText);
			self.items[IDstring]:UpdateItemLink (itemLink);
		end
		
	end

	
end

-----------------------------------------

function Atr_FindScanAndInit (IDstring, itemName)

	return Atr_FindScan (IDstring, itemName, true);
end

-----------------------------------------

function Atr_FindScan (IDstring, itemName, init)

	if (IDstring == nil or IDstring == "" or IDstring == "0") then
		IDstring = "0";
		itemName = "nil";
	end

	if (gAllScans[IDstring] == nil and itemName ~= nil) then	-- if no itemName provided then we can't create
		local scn = {};
		setmetatable (scn, AtrScan);
		gAllScans[IDstring] = scn;
		init = true;
--		zz ("creating scan: ", IDstring, itemName);
	end

	if (init and gAllScans[IDstring] ~= nil) then
--		zz ("initing scan: ", IDstring, itemName);
		
		gAllScans[IDstring]:Init (IDstring, itemName);
	end
	
	return gAllScans[IDstring];
end

-----------------------------------------

function Atr_ClearScanCache ()

	for a,v in pairs (gAllScans) do
		if (a ~= "0") then
			gAllScans[a] = nil;
		end
	end

end

-----------------------------------------

function AtrScan:Init (IDstring, itemName)

	self.IDstring			= IDstring;
	
	if (itemName) then
		self.itemName = itemName;
	end
	
	self.itemLink			= nil;
	self.scanData			= {};
	self.sortedData			= {};
	self.whenScanned		= 0;
	self.lowprice			= BIGNUM;
	self.absoluteBest		= nil;
	self.itemClass			= 0;
	self.itemSubclass		= 0;
	self.itemLevel			= 0;
	self.yourBestPrice		= nil;
	self.yourWorstPrice		= nil;
	self.itemTextColor 		= { 1.0, 1.0, 1.0 };
	self.searchText			= nil;
	
end


-----------------------------------------

function AtrScan:UpdateItemLink (itemLink)

	if (itemLink and self.itemLink == nil) then
	
		self.itemLink = itemLink;

		local _, quality, iLevel, sType, sSubType;

		if (zc.IsBattlePetLink (itemLink)) then
			
			local speciesID, level, breedQuality = zc.ParseBattlePetLink (itemLink)
			
			iLevel	= level;
			quality	= breedQuality;
			
			self.itemClass		= AUCTION_CLASS_BATTLEPET;
			self.itemSubclass	= 0;

		else
			_, _, quality, iLevel, _, sType, sSubType = GetItemInfo(itemLink);

			self.itemClass		= Atr_ItemType2AuctionClass (sType);
			self.itemSubclass	= Atr_SubType2AuctionSubclass (self.itemClass, sSubType);	
		end
			
		self.itemQuality	= quality;
		self.itemLevel		= iLevel;

		self.itemTextColor = { 0.75, 0.75, 0.75 };

		if (quality == 0)	then	self.itemTextColor = { 0.6, 0.6, 0.6 };	end
		if (quality == 1)	then	self.itemTextColor = { 1.0, 1.0, 1.0 };	end
		if (quality == 2)	then	self.itemTextColor = { 0.2, 1.0, 0.0 };	end
		if (quality == 3)	then	self.itemTextColor = { 0.0, 0.5, 1.0 };	end
		if (quality == 4)	then	self.itemTextColor = { 0.7, 0.3, 1.0 };	end
	end

end



-----------------------------------------

function AtrSearch:NumScans()

	if (self.sortedScans) then
		return #self.sortedScans;
	end

	local idstr,scn;
	local count = 0;
	for idstr,scn in pairs (self.items) do
		count = count + 1;
	end

	return count;
end

-----------------------------------------

function AtrSearch:NumSortedScans()

	if (self.sortedScans) then
		return #self.sortedScans;
	end

	return 0;
end

-----------------------------------------

function AtrSearch:GetFirstScan()

	if (self.sortedScans) then
		return self.sortedScans[1];
	end

	local idstr,scn;
	for idstr,scn in pairs (self.items) do
		return scn;
	end
	
	return nil
end


-----------------------------------------

function AtrSearch:Start ()

	if (self.searchText == "") then
		return;
	end
	
	if (Atr_IsCompoundSearch (self.searchText)) then
			
		local _, itemClass = Atr_ParseCompoundSearch (self.searchText);
	
		if (itemClass == 0) then
			Atr_Error_Display (ZT("The first part of this compound\n\nsearch is not a valid category."));
			return;
		end

		self.sortHow = ATR_SORTBY_PRICE_DES;
	end

	-- make sure all the matches in the scan db are in memory
	
	local numpulled = 0;
	
	if (self.exactMatchtext == nil and not self.IDstring and not Atr_IsCompoundSearch (self.searchText) and string.len(self.searchText) > 2) then
		local name, info, itemLink;
		for name, info in pairs(gAtr_ScanDB) do
			if (zc.StringContains (name, self.searchText)) then
				if (type(info) == "table" and info.id) then
					local itemID, suffixID = strsplit(":", info.id);
					if (suffixID == nil) then		-- for now; seems problematic for many green "of the" items
						itemLink = zc.PullItemIntoMemory (itemID, suffixID);
						numpulled = numpulled + 1;
					end
				end
			end
		end
	end

	-- make sure all the items in the shopping list are in memory
	
	if (self.shplist) then
		local n
		for n = 1,self.shplist:GetNumItems() do
			local itemname = zc.TrimQuotes(self.shplist:GetNthItemName (n))
			local dbInfo = gAtr_ScanDB[itemname]
			if (dbInfo and dbInfo.id) then
				zc.PullItemIntoMemory (dbInfo.id);
				numpulled = numpulled + 1;
			end
		end
	end

	gNumNilItemLinks = 0
	
	self.processing_state = KM_SETTINGSORT;
	
	if (Atr_ILevelHist_Init) then
		Atr_ILevelHist_Init()
	end
	
	SortAuctionClearSort ("list");

	BrowseName:SetText (self.searchText);		-- not necessary but nice when user switches to Browse tab

	self.current_page		= 0;
	self.processing_state	= KM_PREQUERY;

	self:Continue();
	
end

-----------------------------------------

function AtrSearch:Abort ()

	if (self.processing_state == KM_NULL_STATE) then
		return;
	end

	self.processing_state = KM_NULL_STATE;
	self:Init();
end

-----------------------------------------

function AtrSearch:CheckForDuplicatePage ()

	local isDup = self.query:CheckForDuplicatePage(self.current_page);

	if (isDup) then
--		zc.msg_red ("DUPLICATE PAGE FOUND: ", "  current_page: ", self.current_page, "  numDupPages: ", self.query.numDupPages);

		self.current_page	= self.current_page - 1;   -- requery the page
		
		self.processing_state = KM_PREQUERY;
	end
		
	return isDup;
end


-----------------------------------------

function AtrSearch:AnalyzeResultsPage()

	self.processing_state = KM_ANALYZING;

	if (self.query.numDupPages > 50) then 	 -- hopefully this will never happen but need check to avoid looping
		return true;						 -- done
	end


	local numBatchAuctions, totalAuctions = Atr_GetNumAuctionItems("list");

	if (self.current_page == 1 and totalAuctions > 5000) then -- give Blizz servers a break (100 pages)
		Atr_Error_Display (ZT("Too many results\n\nPlease narrow your search"));
		return true;  -- done
	end

	local msg

	local slistItemName = Atr_GetShoppingListItem (self)
	if (slistItemName) then
		
		local pageText = "";
		if (self.current_page > 1) then
			pageText = string.format (ZT(": page %d"), self.current_page)
		else
			pageText = "             "
		end

		msg = string.format (ZT("Scanning auctions for %s%s"), slistItemName, pageText);
	elseif (totalAuctions >= 50) then
		msg = string.format (ZT("Scanning auctions: page %d"), self.current_page);
	end

	if (msg) then
		Atr_SetMessage (msg)
	end

	--zz (slistItemName, "current_page: ", self.current_page, "numBatchAuctions: ", numBatchAuctions)

	-- analyze

	local k, g, f
	local numNilOwners = 0

	if (numBatchAuctions > 0) then

		local x;

		for x = 1, numBatchAuctions do

			local name, texture, count, quality, canUse, level, huh, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo("list", x);

			local itemLink = GetAuctionItemLink("list", x);
			
			if (itemLink) then
				local IDstring = zc.ItemIDStrfromLink (itemLink);
				
				if (Atr_ILevelHist_Update) then
					Atr_ILevelHist_Update(itemLink)
				end
				
				local OKitemLevel = true
				if (self.minItemLevel or self.maxItemLevel) then
					local _, _, _, iLevel, _ = GetItemInfo(itemLink);
					if ((self.minItemLevel and iLevel < self.minItemLevel) or (self.maxItemLevel and iLevel > self.maxItemLevel)) then
						OKitemLevel = false
					end
				end
				
				if (OKitemLevel) then
					if (owner == nil) then
						numNilOwners = numNilOwners + 1
					end

					if (self.exactMatchText == nil or zc.StringSame (name, self.exactMatchText)) then

						if (self.items[IDstring] == nil) then
							self.items[IDstring] = Atr_FindScanAndInit (IDstring, name)
						end
						
						local curpage = (tonumber(self.current_page)-1)

						local scn = self.items[IDstring]

						if (scn) then
							scn:AddScanItem (count, buyoutPrice, owner, 1, curpage)
							scn:UpdateItemLink (itemLink)
						end
					end
				end
			else
				gNumNilItemLinks = gNumNilItemLinks + 1
			end
		end
	end
	
	local done = (numBatchAuctions < 50);

	if (done) then
		if (self.shplist) then
			self.shopListIndex = self.shopListIndex + 1
			local nextSearchItem = Atr_GetShoppingListItem (self)
			if (nextSearchItem) then
				self.current_page		= 0
				self.exactMatchText		= nil
				done = false
			end
		end
	end

	if (not done) then
		self.processing_state = KM_PREQUERY;
	end

	return done;
end

-----------------------------------------

function AtrScan:AddScanItem (stackSize, buyoutPrice, owner, numAuctions, curpage)

	local sd = {};
	local i;

	if (numAuctions == nil) then
		numAuctions = 1;
	end

	for i = 1, numAuctions do
		sd["stackSize"]		= stackSize;
		sd["buyoutPrice"]	= buyoutPrice;
		sd["owner"]			= owner;
		sd["pagenum"]		= curpage;

		tinsert (self.scanData, sd);
		
		if (buyoutPrice and buyoutPrice > 0) then
			local itemPrice = math.floor (buyoutPrice / stackSize);

			self.lowprice = math.min (self.lowprice, itemPrice);
		end
	end

end


-----------------------------------------

function AtrScan:SubtractScanItem (stackSize, buyoutPrice)

	local sd;
	local i;

	for i,sd in ipairs (self.scanData) do
		
		if (sd.stackSize == stackSize and sd.buyoutPrice == buyoutPrice) then
			
			tremove (self.scanData, i);
			return;
		end
	end

end


-----------------------------------------

function Atr_IsShoppingListSearch (searchString)
	
	if (searchString == nil) then
		return false;
	end
	
	return zc.StringStartsWith (searchString, "{ ") and zc.StringEndsWith (searchString, " }");
end

-----------------------------------------

function Atr_GetShoppingListFromSearchText (searchString)

	if (Atr_IsShoppingListSearch (searchString)) then
		local len = string.len(searchString);

		local shoppingListName = string.sub (searchString, 3, len-2);
		
		return Atr_SList.FindByName (shoppingListName);
	end
	
	return nil
end

-----------------------------------------

function Atr_GetShoppingListItem (search)

	if (search.shplist) then
		return search.shplist:GetNthItemName (search.shopListIndex);
	end
	
	return nil;
end

-----------------------------------------

function Atr_IsCompoundSearch (searchString)
	
	if (searchString == nil) then
		return false;
	end
	
	return zc.StringContains (searchString, ">") or zc.StringContains (searchString, "/");
end

-----------------------------------------

local function toItemLevel (s)		-- returns nil if not of the form i72 or i277, etc

	if (string.len(s) > 1 and string.sub(s,1,1) == "i") then
		return tonumber (string.sub(s,2))
	end
	
	return nil
end

-----------------------------------------

function Atr_ParseCompoundSearch (searchString)

	local delim = "/";

	if (zc.StringContains (searchString, ">")) then
		delim = ">";
	end

	local tbl	= { strsplit (delim, searchString) };
	
	local queryString	= "";
	local itemClass		= 0;
	local itemSubclass	= 0;
	local minLevel		= nil;
	local maxLevel		= nil;
	local minItemLevel	= nil;
	local maxItemLevel	= nil;
	local prevWasItemClass;
	local n;
	
	for n = 1,#tbl do
		local s = tbl[n];

		local handled = false;

	
		if (not handled and tonumber(s)) then
			if (minLevel == nil) then
				minLevel = tonumber(s);
			elseif (maxLevel == nil) then
				maxLevel = tonumber(s);
			end
			
			handled = true;
			prevWasItemClass = false;
		end
	
		if (not handled and toItemLevel(s)) then
			if (minItemLevel == nil) then
				minItemLevel = toItemLevel(s);
			elseif (maxItemLevel == nil) then
				maxItemLevel = toItemLevel(s);
			end
			
			handled = true;
			prevWasItemClass = false;
		end
		
		if (not handled and prevWasItemClass and itemSubclass == 0) then
			itemSubclass = Atr_SubType2AuctionSubclass (itemClass, s);
			if (itemSubclass > 0) then
				handled = true;
				prevWasItemClass = false;
			end
		end
		
		if (not handled and itemClass == 0) then

			itemClass = Atr_ItemType2AuctionClass (s);

			if (itemClass > 0) then
				prevWasItemClass = true;
				handled = true;
			end
		end
		
		if (not handled) then
			queryString = s;
			handled = true;
		end
	end	

	return queryString, itemClass, itemSubclass, minLevel, maxLevel, minItemLevel, maxItemLevel;
end

-----------------------------------------

function AtrSearch:Continue()

	if (CanSendAuctionQuery()) then

		self.processing_state = KM_IN_QUERY;

		local queryString;

		local itemClass		= 0;
		local itemSubclass	= 0;
		local minLevel		= nil;
		local maxLevel		= nil;
		
		if (Atr_IsCompoundSearch(self.searchText)) then
			queryString, itemClass, itemSubclass, minLevel, maxLevel = Atr_ParseCompoundSearch (self.searchText);
			
		elseif (self.shplist) then
			queryString = Atr_GetShoppingListItem (self)
			
			self.exactMatchText = GetExactMatchText(queryString)
			if (self.exactMatchText) then
				queryString = self.exactMatchText
			end
			
			-- skip nested shopping lists or compound searches
			while (Atr_IsShoppingListSearch(queryString) or Atr_IsCompoundSearch(queryString)) do
				zc.md ("Skipping ", queryString);
				self.shopListIndex = self.shopListIndex + 1
				queryString = Atr_GetShoppingListItem (self)
				if (queryString == nil) then
					break
				end
			end
			
			if (queryString == nil) then
				queryString = "?????";
			end
		else
			queryString = self.searchText;
		end

		queryString = zc.UTF8_Truncate (queryString,63);	-- attempting to reduce number of disconnects

		--zz (queryString, "  page:", self.current_page);
		
		QueryAuctionItems (queryString, minLevel, maxLevel, nil, itemClass, itemSubclass, self.current_page, nil, nil);

		self.query_sent_when	= gAtr_ptime;
		self.processing_state	= KM_POSTQUERY;

		self.current_page		= self.current_page + 1;
	end

end

-----------------------------------------

local gSortScansBy;

-----------------------------------------

local function Atr_SortScans (x, y)

	if (gSortScansBy == ATR_SORTBY_NAME_ASC) then		return string.lower (x.itemName) < string.lower (y.itemName);	end
	if (gSortScansBy == ATR_SORTBY_NAME_DES) then		return string.lower (x.itemName) > string.lower (y.itemName);	end

	local xprice = 0;
	local yprice = 0;
	
	if (x.absoluteBest) then	xprice = zc.round(x.absoluteBest.buyoutPrice/x.absoluteBest.stackSize);		end;
	if (y.absoluteBest) then	yprice = zc.round(y.absoluteBest.buyoutPrice/y.absoluteBest.stackSize);		end;
	
	if (gSortScansBy == ATR_SORTBY_PRICE_ASC) then		return xprice < yprice;		end
	if (gSortScansBy == ATR_SORTBY_PRICE_DES) then		return xprice > yprice;		end

end

-----------------------------------------

function AtrSearch:Finish()

	local finishTime = time();
	
	self.processing_state	= KM_NULL_STATE;
	self.current_page		= -1;
	self.query_sent_when	= nil;

	-- add scans for items that weren't found (at least for items that are in the scan db)
	
	if (self.exactMatchText == nil and not self.IDstring) then
		if (not Atr_IsCompoundSearch (self.searchText)) then
			local name, info;
			for name, info in pairs(gAtr_ScanDB) do
				if (zc.StringContains (name, self.searchText)) then
					if (info.id and self.items[info.id] == nil) then
						local itemID, suffixID = strsplit(":", info.id);
						if (suffixID == nil) then		-- for now; seems problematic for many green "of the" items
							self.items[info.id] = Atr_FindScanAndInit (info.id, name);
							local itemLink = zc.LinkFromItemID (itemID, suffixID);
							if (itemLink) then
								self.items[info.id]:UpdateItemLink (itemLink)
							end
						end
					end
				end
			end
		end
	end
	
	-- add scans for items in the shopping list that weren't found
	
	if (self.shplist) then
		local n
		for n = 1,self.shplist:GetNumItems() do
			local itemname = zc.TrimQuotes(self.shplist:GetNthItemName (n))
			
			local dbInfo = gAtr_ScanDB[itemname]
			if (dbInfo and dbInfo.id) then
				local IDstring = dbInfo.id
				if (self.items[IDstring] == nil) then
					self.items[IDstring] = Atr_FindScanAndInit (IDstring, itemname)
					local itemLink = zc.LinkFromItemID (IDstring);
					if (itemLink) then
						self.items[IDstring]:UpdateItemLink (itemLink)
					end
				end
			else		-- not in scandb
				local IDstring = "***"..itemname				
				self.items[IDstring] = Atr_FindScanAndInit (IDstring, itemname)
			end
		end
	end

	-- create an empty scan if there were no matches
	
	self.sortedScans = nil;
	
	if (self:NumScans() == 0) then

		local dbInfo = gAtr_ScanDB[self.searchText]
		
		if (dbInfo and dbInfo.id) then	-- so that we see the history tab, etc.
			local IDstring = dbInfo.id;
			self.items[IDstring] = Atr_FindScan (IDstring, self.searchText, true);
		else
			self.items["0"] = Atr_FindScan (nil);
		end
	
	end
	
	-- process the scans
	
	local broadcastInfo = {};
	
	local x = 1;
	self.sortedScans = {};
	
	for IDstring,scn in pairs (self.items) do
	
		self.sortedScans[x] = scn;
		x = x + 1;
		
		scn.whenScanned		= finishTime;
		scn.searchText		= self.searchText;

		scn:CondenseAndSort ();

		-- update the fullscan DB

		if (scn.lowprice < BIGNUM) then
		
			if (scn.itemQuality == nil) then
				zc.msg_anm ("|cffff0000Error: scn.itemQuality == nil, scn.itemName: ", scn.itemName);
			end
			
			if (scn.itemQuality ~= nil and (scn.itemQuality + 1 >= AUCTIONATOR_SCAN_MINLEVEL or scn.quality == -1)) then		--  battle pets can be UNKNOWN (-1) quality
								
				Atr_UpdateScanDBprice		(scn.itemName, scn.lowprice);
				Atr_UpdateScanDBclassInfo	(scn.itemName, scn.itemClass, scn.itemSubclass);
				Atr_UpdateScanDBitemID		(scn.itemName, scn.itemLink);
				
				table.insert (broadcastInfo, {i=scn.IDstring, p=scn.lowprice});
			end
		end
	end

	if (gNumNilItemLinks > 0) then
		zc.msg_anm ("Number of nil links found during scan: ", gNumNilItemLinks)
	end

	Atr_Broadcast_DBupdated (#broadcastInfo, "partialscan", broadcastInfo);

	if (Atr_ILevelHist_Print) then
		Atr_ILevelHist_Print()
	end
	
	
	gSortScansBy = self.sortHow;
	table.sort (self.sortedScans, Atr_SortScans);
end

-----------------------------------------

function AtrSearch:ClickPriceCol()

	if (self.sortHow == ATR_SORTBY_PRICE_ASC) then
		self.sortHow = ATR_SORTBY_PRICE_DES;
	else
		self.sortHow = ATR_SORTBY_PRICE_ASC;
	end

	gSortScansBy = self.sortHow;
	table.sort (self.sortedScans, Atr_SortScans);

end

-----------------------------------------

function AtrSearch:ClickNameCol()

	if (self.sortHow == ATR_SORTBY_NAME_ASC) then
		self.sortHow = ATR_SORTBY_NAME_DES;
	else
		self.sortHow = ATR_SORTBY_NAME_ASC;
	end

	gSortScansBy = self.sortHow;
	table.sort (self.sortedScans, Atr_SortScans);
end

-----------------------------------------

function AtrSearch:UpdateArrows()

	Atr_Col1_Heading_ButtonArrow:Hide();
	Atr_Col3_Heading_ButtonArrow:Hide();
	
	if (self.sortHow == ATR_SORTBY_PRICE_ASC) then
		Atr_Col1_Heading_ButtonArrow:Show();
		Atr_Col1_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 0, 1.0);
	elseif (self.sortHow == ATR_SORTBY_PRICE_DES) then
		Atr_Col1_Heading_ButtonArrow:Show();
		Atr_Col1_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 1.0, 0);
	elseif (self.sortHow == ATR_SORTBY_NAME_ASC) then
		Atr_Col3_Heading_ButtonArrow:Show();
		Atr_Col3_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 0, 1.0);
	elseif (self.sortHow == ATR_SORTBY_NAME_DES) then
		Atr_Col3_Heading_ButtonArrow:Show();
		Atr_Col3_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 1.0, 0);
	end
end

-----------------------------------------

function Atr_ClearBrowseListings()
	

	if (CanSendAuctionQuery()) then
		QueryAuctionItems("xyzzy", 43, 43, 0, 7, 0);
		zz ("Atr_ClearBrowseListings succeeded");
	end


end

-----------------------------------------

function Atr_SortAuctionData (x, y)

	return x.itemPrice < y.itemPrice;

end

-----------------------------------------

function AtrScan:CondenseAndSort ()

	----- Condense the scan data into a table that has only a single entry per stacksize/price combo

	self.sortedData	= {};

	local i,sd;
	local conddata = {};

	for i,sd in ipairs (self.scanData) do

		local ownerCode = "x";
		local dataType  = "n";		-- normal
		
		if (sd.owner == UnitName("player")) then
			ownerCode = "y";
--		elseif (Atr_IsMyToon (sd.owner)) then
--			ownerCode = sd.owner;
		end

		local key = "_"..sd.stackSize.."_"..sd.buyoutPrice.."_"..ownerCode..dataType;

		if (conddata[key]) then
			conddata[key].count		= conddata[key].count + 1;
			conddata[key].minpage 	= zc.Min (conddata[key].minpage, sd.pagenum);
			conddata[key].maxpage 	= zc.Max (conddata[key].maxpage, sd.pagenum);
		else
			local data = {};

			data.stackSize 		= sd.stackSize;
			data.buyoutPrice	= sd.buyoutPrice;
			data.itemPrice		= sd.buyoutPrice / sd.stackSize;
			data.minpage		= sd.pagenum;
			data.maxpage		= sd.pagenum;
			data.count			= 1;
			data.type			= dataType;
			data.yours			= (ownerCode == "y");
			
			if (ownerCode ~= "x" and ownerCode ~= "y") then
				data.altname = ownerCode;
			end
			
			if (sd.volume) then
				data.volume = sd.volume;
			end
			
			conddata[key] = data;
		end

	end

	----- create a table of these entries

	local n = 1;

	local i, v;

	for i,v in pairs (conddata) do
		self.sortedData[n] = v;
		n = n + 1;
	end

	-- sort the table by itemPrice

	table.sort (self.sortedData, Atr_SortAuctionData);

	-- analyze and store some info about the data

	self:AnalyzeSortData ();

end

-----------------------------------------

function AtrScan:AnalyzeSortData ()

	self.absoluteBest			= nil;
	self.bestPrices				= {};		-- a table with one entry per stacksize that is the cheapest auction for that particular stacksize
	self.numMatches				= 0;
	self.numMatchesWithBuyout	= 0;
	self.hasStack				= false;
	self.yourBestPrice			= nil;
	self.yourWorstPrice			= nil;

	local j, sd;

	----- find the best price per stacksize and overall -----

	for j,sd in ipairs(self.sortedData) do

		if (sd.type == "n") then

			self.numMatches = self.numMatches + 1;

			if (sd.itemPrice > 0) then

				self.numMatchesWithBuyout = self.numMatchesWithBuyout + 1;

				if (self.bestPrices[sd.stackSize] == nil or self.bestPrices[sd.stackSize].itemPrice >= sd.itemPrice) then
					self.bestPrices[sd.stackSize] = sd;
				end

				if (self.absoluteBest == nil or self.absoluteBest.itemPrice > sd.itemPrice) then
					self.absoluteBest = sd;
				end
				
				if (sd.yours) then
					if (self.yourBestPrice == nil or self.yourBestPrice > sd.itemPrice) then
						self.yourBestPrice = sd.itemPrice;
					end
					
					if (self.yourWorstPrice == nil or self.yourWorstPrice < sd.itemPrice) then
						self.yourWorstPrice = sd.itemPrice;
					end
					
				end
			end

			if (sd.stackSize > 1) then
				self.hasStack = true;
			end
		end
	end
end

-----------------------------------------

function AtrScan:FindInSortedData (stackSize, buyoutPrice)
	local j = 1;
	for j = 1,#self.sortedData do
		sd = self.sortedData[j];
		if (sd.stackSize == stackSize and sd.buyoutPrice == buyoutPrice and sd.yours) then
			return j;
		end
	end
	
	return 0;
end


-----------------------------------------

function AtrScan:FindMatchByStackSize (stackSize)

	local index = nil;

	local basedata = self.absoluteBest;

	if (self.bestPrices[stackSize]) then
		basedata = self.bestPrices[stackSize];
	end

	local numrows = #self.sortedData;

	local n;

	for n = 1,numrows do

		local data = self.sortedData[n];

		if (basedata and data.itemPrice == basedata.itemPrice and data.stackSize == basedata.stackSize and data.yours == basedata.yours) then
			index = n;
			break;
		end
	end

	return index;
	
end

-----------------------------------------

function AtrScan:FindMatchByYours ()

	local index = nil;

	local j;
	for j = 1,#self.sortedData do
		sd = self.sortedData[j];
		if (sd.yours) then
			index = j;
			break;
		end
	end

	return index;

end

-----------------------------------------

function AtrScan:FindCheapest ()

	local index = nil;

	local j;
	for j = 1,#self.sortedData do
		sd = self.sortedData[j];
		if (sd.itemPrice > 0) then
			index = j;
			break;
		end
	end

	return index;

end


-----------------------------------------

function AtrScan:GetNumAvailable ()

	local num = 0;

	local j, data;
	for j = 1,#self.sortedData do

		data = self.sortedData[j];
		num = num + (data.count * data.stackSize);
	end
	
	return num;
end

-----------------------------------------

function AtrScan:IsNil ()

	if (self.itemName == nil or self.itemName == "" or self.itemName == "nil") then
		return true;
	end
	
	return false;
end


-----------------------------------------

function Atr_GetDBsize()

	local n = 0;
	local a,v;

	for a,v in pairs (gAtr_ScanDB) do
		n = n + 1;
	end
	
	return n;
end


-----------------------------------------

function Atr_UpdateScanDBitemID (itemName, itemLink)

	if (itemLink == nil) then
		return;
	end
	
	if (not gAtr_ScanDB[itemName]) then
		gAtr_ScanDB[itemName] = {};
	end

	gAtr_ScanDB[itemName].id = zc.ItemIDStrfromLink (itemLink);
end
	
-----------------------------------------

function Atr_UpdateScanDBclassInfo (itemName, class, subclass)

	if (not gAtr_ScanDB[itemName]) then
		gAtr_ScanDB[itemName] = {};
	end

	gAtr_ScanDB[itemName].cc = class;
	gAtr_ScanDB[itemName].sc = subclass;

--	zc.md ("Setting class info for:", itemName, "    ", class, subclass);

end

-----------------------------------------

function Atr_UpdateScanDBprice (itemName, currentLowPrice, db)

	if (currentLowPrice == nil) then
		zc.msg_badErr ("currentLowPrice in NIL!!!!!!", itemName)
--		zc.printstack()
		return
	end

	if (type(currentLowPrice) ~= "number") then
		zc.msg_badErr ("currentLowPrice in not a number !!!!!!", type(currentLowPrice), itemName)
--		zc.printstack()
		return
	end

	if (db == nil) then
		db = gAtr_ScanDB;
	end

	if (db and type (db) ~= "table") then
		zc.msg_badErr ("Scanning history database appears to be corrupt")
		zc.msg_badErr ("db:", db)
		return nil
	end

	if (not db[itemName]) then
		db[itemName] = {};
	end

	db[itemName].mr = currentLowPrice;
	
	local daysSinceZero = Atr_GetScanDay_Today();

	local lowlow  = db[itemName]["L"..daysSinceZero];
	local highlow = db[itemName]["H"..daysSinceZero];

	local olow	= lowlow;
	local ohigh = highlow;
	
	if (highlow == nil or currentLowPrice > highlow) then
		db[itemName]["H"..daysSinceZero] = currentLowPrice;
		highlow = currentLowPrice;
	end

	-- save memory by only saving lowlow when different from highlow
	
	local isLowerThanLow		= (lowlow ~= nil and currentLowPrice < lowlow);
	local isNewAndDifferent		= (lowlow == nil and currentLowPrice < highlow);
	
	if (isLowerThanLow or isNewAndDifferent) then
		db[itemName]["L"..daysSinceZero] = currentLowPrice;
--		zz (itemName, "currentLowPrice:", currentLowPrice, "highlow:", highlow, "lowlow:", lowlow, "ohigh:", ohigh, "olow:", olow);
	end

	if (db[itemName]["po"]) then	-- unmark this item so it isn't purged
		db[itemName]["po"] = nil;
	end
end

-----------------------------------------

function Atr_PurgeObsoleteItems ()

	-- one time removal of old items - called after a full scan

	local a = 0
	local b = 0
	local potentials = 0;
	local doPurge, mostRecentDay, key, price, name, itemInfo
	
	local todayDay	= Atr_GetScanDay_Today()
	
	for name, itemInfo in pairs (gAtr_ScanDB) do
		
		doPurge = false;
		
		if (type(itemInfo) == "table") then

			mostRecentDay = -1

			for key, price in pairs (itemInfo) do
				char1 = string.sub (key, 1, 1)
				if (char1 == "H") then
					day = tonumber (string.sub(key, 2))
					mostRecentDay	= math.max (day, mostRecentDay)
				end
			end

			if (itemInfo["po"]) then
				potentials = potentials + 1;
			end
			
			if (itemInfo["po"] and todayDay - mostRecentDay > 10) then
				doPurge = true;
			end
		end
		
		if (doPurge) then
			gAtr_ScanDB[name] = nil
			a = a + 1
		end
		
		b = b + 1
	end

--	zz ("potentials:", potentials);
--	zz ("purged:", a, "out of", b)
end

-----------------------------------------

function Atr_PrunePostDB()

	-- remove old items from the posting history database

	if (AUCTIONATOR_PRICING_HISTORY == nil) then
		return;
	end

	local now = time();
	local x = 0;
	local total = 0;
	
	local tempDB = {};
	zc.CopyDeep (tempDB, AUCTIONATOR_PRICING_HISTORY);

	for itemName, info in pairs(tempDB) do

		local recentWhen = 0;
		local tag, hist;
		
		for tag, hist in pairs (info) do
			if (tag ~= "is") then
				local when, type, price = ParseHist (tag, hist);

				if (when > recentWhen) then
					recentWhen  = when;
				end
			end
		end
		
		if (now - recentWhen > 180 * 86400) then
			AUCTIONATOR_PRICING_HISTORY[itemName] = nil;
			--zc.md (itemName, "   ", date("%A, %B %d %Y", recentWhen));
			x = x + 1;
		end
		
		total = total + 1;
	end
	
	collectgarbage  ("collect");
	
	if (x > 0) then
		zc.md (x, "of", total, "items pruned from post DB");
	end
end

-----------------------------------------

function Atr_MigtrateMaxHistAge()		-- 21 was too much

	if (AUCTIONATOR_DB_MAXHIST_AGE and AUCTIONATOR_DB_MAXHIST_AGE ~= 21 and AUCTIONATOR_DB_MAXHIST_AGE ~= -1) then
		zz ("AUCTIONATOR_DB_MAXHIST_AGE:", AUCTIONATOR_DB_MAXHIST_AGE, "AUCTIONATOR_DB_MAXHIST_DAYS:", AUCTIONATOR_DB_MAXHIST_DAYS)
		AUCTIONATOR_DB_MAXHIST_DAYS = AUCTIONATOR_DB_MAXHIST_AGE;
	end
		
	AUCTIONATOR_DB_MAXHIST_AGE = -1;
end

-----------------------------------------

function Atr_PruneScanDB(verbose)

	local start = time();

	collectgarbage  ("collect");

	local startMem = Atr_GetAuctionatorMemString();

	local dbCopy = {};

	local todayDays	= Atr_GetScanDay_Today();

	Atr_MigtrateMaxHistAge();

	local histCutoff	= todayDays - AUCTIONATOR_DB_MAXHIST_DAYS;
	local itemCutoff	= todayDays - AUCTIONATOR_DB_MAXITEM_AGE;
	
	local x = 0;
	local h = 0;
	local y = 0;
	local z = 0;
	
	local key, price, char1, day, doCopy;

	if (gAtr_ScanDB and type (gAtr_ScanDB) ~= "table") then
		zc.msg_badErr ("Scanning history database appears to be corrupt")
		zc.msg_badErr ("gAtr_ScanDB:", gAtr_ScanDB)
		return
	end
	
	for itemName, info in pairs (gAtr_ScanDB) do
	
		local mostRecentDay = -1;
		
		-- first pass over item
		
		for key, price in pairs (info) do
			char1 = string.sub (key, 1, 1);
			if (char1 == "H") then
				day = tonumber (string.sub(key, 2));
				mostRecentDay	= math.max (day, mostRecentDay);
			end
		end
		
		-- decide if the item should be retained
		
		if (mostRecentDay == -1 or mostRecentDay >= itemCutoff) then
		
			dbCopy[itemName] = {};
			y = y + 1;
			
			for key, price in pairs (info) do			-- second pass over item
				doCopy = true;
				
				char1 = string.sub (key, 1, 1);
				if (char1 == "H" or char1 == "L") then
					day = tonumber (string.sub(key, 2));
					if (day < histCutoff and day ~= mostRecentDay) then
						doCopy = false;
						h = h + 1;
					end
				end
				
				if (doCopy) then
					dbCopy[itemName][key] = price;
					z = z + 1;
				end
			end
		else
			x = x + 1;
		end
		
		
	end

	zc.ClearTable (gAtr_ScanDB);
	zc.CopyDeep (gAtr_ScanDB, dbCopy);
	
	dbCopy = nil;
	
	collectgarbage  ("collect");

	if (verbose or Atr_IsDev) then
		local endMem = Atr_GetAuctionatorMemString();
--		zc.msg_anm ("Memory usage went from", startMem, "to", endMem);
	end

--	zc.md ("Historical prices pruned: ", h, "out of", h+z, "   Items pruned: ", x, "out of", x+y, "   Time taken: ", time() - start);
	
end

-----------------------------------------

function Atr_BuildSortedScanHistoryList (itemName)

	local currentPane = Atr_GetCurrentPane();
	
	local todayScanDay = Atr_GetScanDay_Today();
	
	-- build the sorted history list

	currentPane.sortedHist = {};

	if (gAtr_ScanDB[itemName]) then
		local n = 1;
		local key, highlowprice, char1, day, when;
		for key, highlowprice in pairs (gAtr_ScanDB[itemName]) do
		
			char1 = string.sub (key, 1, 1);
		
			if (char1 == "H") then

				day = tonumber (string.sub(key, 2));

				when = gScanHistDayZero + (day *86400);

				local lowlowprice = gAtr_ScanDB[itemName]["L"..day];
				if (lowlowprice == nil) then
					lowlowprice = highlowprice;
				end

				highlowprice = tonumber (highlowprice)
				lowlowprice  = tonumber (lowlowprice)

				currentPane.sortedHist[n]				= {};
				currentPane.sortedHist[n].itemPrice		= zc.round ((highlowprice + lowlowprice) / 2);
				currentPane.sortedHist[n].when			= when;
				currentPane.sortedHist[n].yours			= true;
				currentPane.sortedHist[n].type			= "n";

				if (day == todayScanDay) then
					currentPane.sortedHist[n].whenText = ZT("Today");
				elseif (day == todayScanDay - 1) then
					currentPane.sortedHist[n].whenText = ZT("Yesterday");
				else
					currentPane.sortedHist[n].whenText = date("%A, %B %d", when);
				end
				
				n = n + 1;
			end
		end
	end

	table.sort (currentPane.sortedHist, Atr_SortHistoryData);

	if (#currentPane.sortedHist > 0) then
		return currentPane.sortedHist[1].itemPrice;
	end

end

-----------------------------------------

function Atr_GetScanDay_Today()

	return (math.floor ((time() - gScanHistDayZero) / (86400)));

end

-----------------------------------------

function Atr_GetNumAuctionItems (which)

	local numBatchAuctions, totalAuctions = GetNumAuctionItems(which);
	
	local returnTotalAuctions = totalAuctions
	
	if (totalAuctions > 500000 or totalAuctions < 0) then
		totalAuctions = numBatchAuctions;
	end
	
	return numBatchAuctions, totalAuctions, returnTotalAuctions

end




